function mdl = slcfullgauss(mu, sigma, cachelevel, ensym)
%SLCFULLGAUSS Constructs a Gaussian model with full form covariance
%
% [ Syntax ]
%   - mdl = slcfullgauss();
%   - mdl = slcfullgauss(mu, sigma);
%   - mdl = slcfullgauss(mu, sigma, cachelevel);
%   - mdl = slcfullgauss(mu, sigma, cachelevel, false);
% 
% [ Arguments ]
%   - mu:           The mean vector(s) of the Gaussian distribution
%   - sigma:        The covariance matrix(s) of the Gaussian distribution
%   - mdl:          The constructed model object
%
% [ Description ]
%   - mdl = slcfullgauss() constructs an empty Gaussian model object.
%
%     This object typically serves as the basis for following estimation.
%     A conventional way to estimate a model is
%       \{
%           mdl = estimate(slcfullcov(), X, ...)
%       \}
%     Please refer to the help of estimate method for details.
%       
%   - mdl = slcfullgauss(mu, sigma) constructs a Gaussian model object 
%     with given mean vector and covariance matrix. 
%
%     To construct a single model, mu should be a d x 1 vector, while
%     sigma can be given in either of the following forms:
%       - d x d matrix to specify the whole covariance matrix
%       - d x 1 vector to specify a diagonal covariance matrix with
%         the diagonal entries
%       - a scalar to specify a isotropic covariance matrix with
%         the given variance
%
%     Note that you can also construct a set of Gaussian models as 
%     follows. Suppose, you are to make K models. Then you should input 
%     mu as a d x K matrix, with each column giving the mean vector of 
%     a particular model. And the sigma can be given in either of the
%     following forms:
%       - d x d x K matrix, with each page giving a covariance matrix
%       - d x K matrix, with each column giving the diagonal entries
%         of a diagonal covariance matrix
%       - 1 x K matrix, with each element giving the variance of a 
%         isotropic covariance matrix
%     
%     For convenience, the function supports the following shortcut for
%     you to construct a set of models whose initial covariance matrices
%     are the same. You can do this by using the {A} syntax, where A
%     is either a d x d matrix, d x 1 vector, or a scalar. The matrix in 
%     a cell tells the function to treat it as the covariance shared by
%     all models.
%
%   - mdl = slcfullgauss(mu, sigma, cachelevel). With this syntax, you
%     can control the level of pre-computation caching. There are four
%     levels:
%       \{:
%           - 0:        cache nothing
%           - 1:        cache useful scalars, 
%                       including log(det(cov)), trace(cov), and 
%                       mu^T cov^{-1} mu
%           - 2:        cache useful vectors in addition to the above
%                       including cov^{-1} mu
%           - 3:        cache cov^{-1} in addition to the above
%       \:}
%     By default, cache 3 is used. 
%
%   - mdl = slcfullgauss(mu, sigma, cachelevel, false). Using this syntax, 
%     you can ensure that the function does not explicitly enforcing 
%     symmetry of the matrix. (By default, this will be done, when the 
%     sigma is input in full form.)
%     
% [ History ]
%   - Created by Dahua Lin, on Dec 23, 2007
%

%% main

% parse and verify mu and sigma

if nargin == 0  % empty
    mu = [];
    sigma = [];
    cachelevel = 0;
    
    cov_shared = false;
    
else  % parameter given
    
    if nargin < 2
        error('sltoolbox:slcfullgauss:slcfullgauss:invalidarg', ...
            'The sigma should be given.');
    end
    
    if nargin < 3
        cachelevel = 3;
    end
    
    if nargin < 4
        ensym = true;
    end
    
    if ensym
        fsym = @(x) (x + x') * 0.5;
    end
    
    % mu
    
    assert(isnumeric(mu) && ndims(mu) == 2, ...
        'sltoolbox:slcfullgauss:slcfullgauss:invalidarg', ...
        'mu should be a numeric matrix.');    
    [d, K] = size(mu);
    
    if K > 1
        mu = slslice(mu, 1);
    end
    
    % sigma        
    
    if isnumeric(sigma)
        assert(ndims(sigma) <= 3, ...
            'sltoolbox:slcfullgauss:slcfullgauss:invalidarg', ...
            'The number of dimensions of sigma should not exceed 3');
        
        [d1, d2, d3] = size(sigma);
        
        if d1 == d && d2 == d && d3 == K
            if K == 1
                if ensym
                    sigma = fsym(sigma);
                end
            else
                if ensym
                    sigma = cellfun(fsym, slslice(sigma, 2), ...
                        'UniformOutput', false);
                else
                    sigma = slslice(sigma, 2);
                end
            end
            
        elseif d1 == d && d2 == K
            if K == 1
                sigma = diag(sigma);
            else
                sigma = cellfun(@diag, ...
                    slslice(sigma, 1), 'UniformOutput', false);            
            end
            
        elseif d1 == 1 && d2 == K
            if K == 1
                sigma = diag(sigma * ones(d, 1));
            else
                sigma = cellfun(@(x) diag(x * ones(d, 1)), ...
                    slslice(sigma, 0)', 'UniformOutput', false);
            end
            
        else
            error('sltoolbox:slcfullgauss:slcfullgauss:invalidarg', ...
                'The size of sigma is invalid.');
        end
        
        cov_shared = false;
                        
    elseif iscell(sigma) && numel(sigma) == 1
        
        sigma = sigma{1};
       
        assert(isnumeric(sigma) && ndims(sigma) == 2, ...
            'sltoolbox:slcfullgauss:slcfullgauss:invalidarg', ...
            'The shared sigma should be a numeric matrix.');
        
        [d1, d2] = size(sigma);
        
        if d1 == d && d2 == d
            if ensym
                sigma = fsym(sigma);
            end
            
        elseif d1 == d && d2 == 1
            sigma = diag(sigma);
            
        elseif d1 == 1 && d2 == 1
            sigma = diag(sigma * ones(d, 1));
            
        else
            error('sltoolbox:slcfullgauss:slcfullgauss:invalidarg', ...
                'The size of sigma is invalid.');
        end
        
        cov_shared = true;
                   
    else
        error('sltoolbox:slcfullgauss:slcfullgauss:invalidarg', ...
            'The specified sigma is invalid.');
    end
    
end

% make struct

mdl = class(struct(...
    'mu', mu, ...
    'sigma', sigma, ...
    'clevel', 0, ...        % cache level
    'ldsm', [], ...         % log(det(cov))
    'trsm', [], ...         % trace(cov)
    'invsm', [], ...        % inv(cov)
    'ism_mu', [], ...       % inv(cov) * mu
    'mu_ism_mu', [] ...     % mu^T * inv(cov) * mu
    ), ...
    'slcfullgauss');

if cachelevel > 0
    mdl = cache(mdl, cachelevel, cov_shared);
end







